package gbench.sandbox.neo4j.draft;

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import gbench.common.tree.LittleTree.IRecord;
import gbench.commonApp.jdbc.Neo4jApp;
import gbench.commonApp.jdbc.Neo4jApp.Graph;
import gbench.commonApp.jdbc.Neo4jApp.Graph.VertexInfo;

import static gbench.common.tree.LittleTree.MFT;
import static gbench.common.tree.LittleTree.IRecord.FMT;
import static gbench.common.tree.LittleTree.IRecord.KVS2REC;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.IRecord.obj2num;
import static gbench.common.tree.LittleTree.KVPair.KVP;
import static gbench.common.tree.LittleTree.SimpleRecord.REC2;

/**
 * 循环Loop
 * @author gbench
 *
 */
public class MyComputionalGraph2 {
    
    /**
     * 计算引擎
     * @author gbench
     *
     */
    class ComputeEngine{
        
        /**
         * 计算引擎
         */
        public ComputeEngine(){
            this(null,null);
        }
        
        /**
         * 计算引擎
         * @param vlbl
         * @param elbl
         */
        public ComputeEngine(final String vlbl,final String elbl){
            this.vlbl = vlbl==null?"NUM":vlbl;
            this.elbl = elbl==null?"OP":elbl;
            neo4jApp.set_intialize_handler( g -> {// 图构架器的初始化
                g.setVertex_label_renderer(vtx->this.vlbl); // 顶点标号,注意这里需要加入 this 前缀，否则就引用了 参数中的数值了
                g.setEdge_label_renderer(edg->this.elbl);// 运标号,注意这里需要加入 this 前缀，否则就引用了 参数中的数值了
            }, false);// 初始化结构
        }
        
        /**
         * 绘图操作
         * @param g 图操作
         */
        public void plot(Neo4jApp.Graph g) {
            final var edgeLbls = Stream.of(elbl)
                .filter(e->e!=null).collect(Collectors.joining("|")); // 提取所有的边的label
            Neo4jApp.getJdbc().withTransaction(sess->{// 查看买卖关系
                sess.sqlexecute(MFT("match (a)-[e:{0}]->(b) delete a,e,b",edgeLbls));//删除表
                sess.sqlexecute(MFT("create {0}",g));// 创建数据表
                final var sql = MFT("match (a)-[e:{0}]->(b) return a,e,b",edgeLbls);// 查询表
                System.out.println(sql);
                var mm = sess.sql2records(sql);
                System.out.println(FMT(mm));
                sess.sqlexecute(MFT("match p = (a)-[e:{0}]->(b) foreach ( n in nodes(p) | set n.marked=TRUE )",edgeLbls));// 更新顶点数据:设置标记
            });// withTransaction
        }
        
        /**
         * 数据框
         * @param g 图形对象
         * @param dataframe 数据框
         */
        public void execute(final Graph g, final IRecord dataframe) {
            run(g.getStarts(),dataframe);
        }
        
        /**
         * 节点运行
         * @param activeVertices 激活节点序列
         * @param dataframe 数据框
         */
        public void run(List<Graph.VertexInfo> activeVertices,IRecord dataframe) {
            if ( activeVertices.size() >0 ) {// 步骤执行:存在激活变量
                final var nextOps = activeVertices.stream().collect(Collectors.groupingBy(VertexInfo::getNexts));// 获取下一步的激活顶点
                run( 
                    nextOps.entrySet().stream() // 运算顶点数据流化,以方便使用函数化编程编程的管道式风格。
                    .filter(e->e.getKey().size()>0) // 确保存在下一步的计算顶点：即至少为1.这是判断运算结束条件的关键。比如E的下一步计算顶点就不存在，即程序运行到E就计算结束了。
                    .parallel() // 开启并行计算
                    .map( call -> {// 方法调用解析:call是一个完整的方法调用 call.getKey()是方法对象。 call.getValue()是方法调用的形式参数列表。实际菜蔬需要再数据帧给予寻找。
                        final var methods = call.getKey(); // 方法调用的方法对象
                        final var formal_args = call.getValue(); // 形式参数 即形参
                        final var actual_args = REC();// 实际参数即实参
                        final var method = methods.get(0);// 提取操作对象:运算顶点
                        final var fx = method.getProperty("fx"); // 方法名称,即运算处理器的指令名称, fx 表示  f(x) 即函数的的意思。
                        final var output_symbol = method.getName();// 输出变量符号(method名称就是输出变量的符号)即运算结果的变量符号:输出符号,亦即中间结果变量的符号。 用于把计算过程推进到下一运算阶段。
                        
                        System.out.println(method.getName()+":"+fx+"----->"+formal_args);// 输出语句命令
                        method.getInEdges().stream()// 以actual_args为实际参数,调用方法fx:即指令运行
                        .sorted((e1,e2)-> Stream.of(e1,e2).map(e->e.attr("pos",0)).map(obj2num).map(Number::intValue)
                            .reduce((a,b)->a-b).get()) // 对边按照pos参数进行排序。以保证对实参的添加可以保证actual_args可以按照method的设计的参数顺序进行填充/追加
                        .forEach( edge -> { // 输入进来的边:用来表示参数实装如方法调用的关系。
                            final var symbol = edge.startNode().getName();// 输入边上的变量符号
                            System.out.println("【"+edge+"】"+symbol);
                            actual_args.add(symbol,dataframe.get(symbol));// 从数据帧中读取参数值
                        });// method.getInEdges().forEach
                        
                        System.out.println("实际参数"+actual_args);// 实际参数
                        if ( actual_args.values().stream().filter(e->e==null).count()>0 ) {// 含有未解析变量
                            System.out.println("函数参数不足不予激活顶点"+output_symbol+": "+fx+" ("+actual_args+")\n"); // 存在没有解析的变量，不予进行数据计算
                            return null;
                        } else { // actual_args 配置完备,可以完成计算。
                            System.out.println("计算符号("+output_symbol+"): "+fx+" ("+actual_args+")");
                            final var result = processor.eval(fx, actual_args.values());// 方法计算的返回值。
                            dataframe.add(output_symbol,result);// 把计算结果存入数据帧。
                            System.out.println("符号:"+output_symbol+",更新数据帧::"+dataframe+"\n");
                            final var vtx = method.jumpTo(v->v.getVertexByName(output_symbol)); // 跳转到output_symbol的目标节点。将计算过程推入下一个 阶段。
                            return vtx;// 把vtx 推送到 activeVertices
                        }// actual_args 配置完毕
                    }).filter(e->e!=null).collect(Collectors.toList()) // 提取出非空的顶点 作为 新一轮的 activeVertices，进行循环给与递归调用。
                    , dataframe 
                );// run 节点运行
            }// if ( activeVertices.size() >0 ) {// 步骤执行:存在激活变量
        }
        
        /**
         * 生成一个图结构
         * @param edgeLineInfos 边结构信息
         * @return Graph 图结构
         */
        public Graph getGraph(IRecord edgeLineInfos) {
            System.out.println("输入边定义");
            edgeLineInfos.foreach((path,defination)->System.out.println(path+"--->"+defination));// edgeLineInfos
            final var g = neo4jApp.graph(edgeLineInfos);
            return g;
        }
        
        /**
         * 
         * @param definations 图定义
         * @return 
         */
        public IRecord parse(IRecord definations) {
           return KVS2REC(definations.applyForKvs( IRecord::STRING2REC, e->(IRecord)e ) // 转换成一个 IRecord,IRecord的数据结构
            .entrySet().stream().flatMap( e-> { // 谓词动作拆解
                final var part2 = e.getValue(); // 结束顶点
                final var part1 = e.getKey(); // 开始顶点
                if(part1.size()<2) { // 单顶点
                    return Stream.of( KVP (
                        MFT( "{0}",part1.get(0)), // 单顶点边的顶点名称
                        REC(part2.aoks(k->"$"+k)) // 单顶点边的属性:增$前缀表示这是一个顶点属性。
                    )); // 为顶点追加属性:把边的属性拷贝到单顶点属性上去
                }else { // 双顶点
                    final var params_part = part1; // 参数部分，开始点：操作数
                    final var method_part = part2; // 方法部分，结束点:操作子
                    return params_part.kvstream().map( kvp -> KVP( // 生成一个KVPair(key:顶点序号 ,value:顶点符号)
                        // fx的方法参数
                        MFT( "{0}/{1}", kvp.get("value"), method_part.get("name")), // 双顶点边的 路径表达
                        
                        // fx的返回值结果
                        part2.derive(REC( "pos" , MFT("{0}",kvp.get("key")),// 参数位置,注意pos参数一定腰围数值类型，以保证流程解析过程可以通过pos来获取正确参数位置。
                             "name", MFT("{0}/{1}",kvp.get("value"), method_part.get("name")), // 边的名称路径
                             "method", method_part.get("method"),// 方法名称
                             "1#fx", method_part.get("method")// 代表fx
                        ))// 构造结果顶点的结构:结果顶点可能是中间顶点也可以是最终中顶点。
                    ));// 把startNode 和  endNode 拼接成 双顶点的边。
                }// part1->part2的边转换。
            }));// 计算图的图表结构。
        }
        
        /**
         * 图形渲染
         * @param defination 图定义
         * @return Graph
         */
        public Graph render(final IRecord definations) {
            return getGraph(parse(definations));
        }
        final Neo4jApp neo4jApp = Neo4jApp.newInstance("",null,null); // 创建图的应用App
        final String vlbl;// 顶点的label
        final String elbl;// 边的label
        final IRecord processor = REC(// 指令解析/处理器
            "add",(Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a+b).get()), // 加法运算
            "minus",(Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a-b).get()), // 减法运算
            "mul",(Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a*b).get()), // 乘法运算
            "div",(Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a/b).get()) // 除法运算
        );// 指令处理器
    }
    
    @Test
    public void foo() {
        
        // 运算指令列表，所谓运算指令就是可以绑定到图的顶点上的运算程序。只要为addProto 衍化/赋予（derive）一个顶点名称 name属性：
        // 比如 addProto.derive("name","A") 就创建见了一个拥有addProto运算能力的图顶点。
        final var addProto = REC( "method","add", "description","加法"); // 加法运算
        final var minusProto = REC( "method","minus", "description","减法"); // 减法运算
        final var mulProto = REC( "method","mul", "description","乘法"); // 乘法运算
        final var divProto = REC( "method","div", "description","除法"); // 除法运算
        
        /**
         * 表达式:E=d/((a1+a2+a3)*(b1-b2-b3)) 的计算图
         */
        final var definations = REC2(// 计算图的结构，这里采用 小写字符表达 简单顶点，大写字符表示 高阶顶点即边
            // 表达式的项目
            "a1,a2,a3", addProto.derive("name","A","$description","输入值占位符，程序启动时候输入","$category","简单顶点", "title","输入运算"), // 方法参数 , 方法名称
            "b1,b2,b3", minusProto.derive("name","B","$description","输入值占位符，程序启动时候输入","$category","简单顶点", "title","输入运算"), // 方法参数 , 方法名称
            "A,B",      mulProto.derive("name","C"), // 方法参数 , 方法名称
            "d,C",      divProto.derive("name","E"), // 方法参数 , 方法名称
            
            // 变量说明
            "A",        REC("description","a1+a2+a3的结果","category","高阶顶点(边)"), // 顶点属性定义
            "B",        REC("description","b1-b2-b3的结果","category","高阶顶点(边)"), // 顶点属性定义
            "C",        REC("description","A*B的结果","category","高阶顶点(边)"), // 顶点属性定义
            "d",        REC("description","参数变量,程序启动时给予设置","category","简单顶点"), // 顶点属性定义
            "E",        REC("description","d/C的结果即表达式:d/((a1+a2+a3)*(b1-b2-b3))的结果","category","高阶顶点(边)") // 顶点属性定义
        );// 图结构
        
        final var engine = new ComputeEngine(null, null);
        final var g = engine.render(definations);
        final var dataframe = REC("a1",1,"a2",2,"a3",3,"b1",4,"b2",5,"b3",6,"d",84);// 数据帧，数据帧中的符号，即运算过程中的变量铭恒，所以数据帧是一个变量池。是计算的过程记忆(memory)
        engine.execute(g, dataframe);
        System.out.println("\n计算结果为:");
        System.out.println(dataframe.toString2());// 输出数据帧
        final var e_value = dataframe.evalExpr("#d/((#a1+#a2+#a3)*(#b1-#b2-#b3))");
        System.out.println("REC表达式计算的E值："+e_value);
        System.out.println("计算图与表达式的符号计算的结果是否一致："+(e_value.equals(dataframe.dbl("E"))));
        System.out.println(g);
        //图绘制
        engine.plot(g);
    }
}
